package com.xdqzy.baseline.common.manager;

import com.xdqzy.baseline.common.annotation.CustomCacheKey;
import com.xdqzy.baseline.common.utils.ReflectUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Resource;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @version V1.0
 * @Title: redis缓存管理
 * @ClassName: com.xdqzy.baseline.common.manager.RedisCacheManager.java
 * @Description:
 * @author: winter
 * @date: 2020-10-26 22:54
 */
@Slf4j
@Component
public class RedisCacheManager {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private final Map<String, LoaderContext> loaderMap = Maps.newConcurrentMap();

    /**
     * 注册缓存查询
     *
     * @param module
     * @param keyPrefix
     * @param cacheLoader
     */
    public void registerLoader(String module, String keyPrefix, IDbCacheLoader<?, ?> cacheLoader) {
        Assert.noNullElements(Lists.newArrayList(module, keyPrefix), "模块和key前缀不能为null");
        loaderMap.put(module, new LoaderContext(keyPrefix, cacheLoader, module));
        log.info("注册缓存模块:{} ,key前缀 :{}", module, keyPrefix);
    }

    /**
     * 批量查询缓存
     *
     * @param module 缓存模块
     * @param keys   key集合
     * @param <U>    key的泛型
     * @param <R>    value的泛型
     * @return
     */
    public <U extends Serializable, R> Map<U, R> batchGetData(String module, Collection<U> keys) {
        if (CollectionUtils.isEmpty(keys)) {
            return Collections.emptyMap();
        }
        LoaderContext loaderContext = loaderMap.get(module);
        if (loaderContext == null) {
            log.warn("未注册模块:{}", module);
            return Collections.emptyMap();
        }
        IDbCacheLoader<U, R> loader = (IDbCacheLoader<U, R>) loaderContext.loader;
        Map<U, String> keySet = keys.stream().filter(Objects::nonNull).collect(Collectors.toMap(c -> c, loaderContext::buildKey));
        //从redis查询的结果
        List<Object> objects = Optional.ofNullable(redisTemplate.opsForValue().multiGet(keySet.values())).orElse(Collections.emptyList());
        Map<U, R> objectMap = (Map<U, R>) objects.stream().filter(Objects::nonNull).collect(Collectors.toMap(o -> loaderContext.convert(o).getKey(), o -> o));
        //全部命中
        if (objectMap.size() == keys.size()) {
            return objectMap;
        }
        List<U> leftKeys = keySet.keySet().stream().filter(c -> !objectMap.containsKey(c)).collect(Collectors.toList());
        //没有命中缓存的
        Map<U, R> leftValueMap = (Map<U, R>) Optional.ofNullable(loader.batchLoad(leftKeys)).orElse(Collections.emptyList())
                .stream().map(loaderContext::convert).filter(Objects::nonNull).collect(Collectors.toMap(e -> ((Map.Entry) e).getKey(), e -> ((Map.Entry) e).getValue()));
        objectMap.putAll(leftValueMap);
        RedisSerializer<String> keySerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
        RedisSerializer<Object> valueSerializer = (RedisSerializer<Object>) redisTemplate.getValueSerializer();
        if (!CollectionUtils.isEmpty(leftValueMap)) {
            //将没有命中的放到缓存
            Map<String, R> redisValueMap = leftValueMap.entrySet().stream().collect(Collectors.toMap(e -> loaderContext.buildKey(e.getKey()), Map.Entry::getValue));
           //利用管道写入redis
            redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
                redisValueMap.forEach((k, v) ->
                        connection.set(keySerializer.serialize(k), valueSerializer.serialize(v), Expiration.seconds(loader.ttlSeconds()), RedisStringCommands.SetOption.UPSERT)
                );
                return null;
            });

            log.info("将模块:{} key :{} 存入redis成功", module, redisValueMap.keySet());
        }
        return objectMap;
    }

    /**
     * 获取单个数据
     *
     * @param module
     * @param key
     * @param <U>
     * @param <R>
     * @return
     */
    public <U extends Serializable, R> R singleGetData(String module, U key) {
        if (key == null) {
            log.warn("key 无效,查询模块:{}", module);
            return null;
        }
        LoaderContext loaderContext = loaderMap.get(module);
        if (loaderContext == null) {
            log.warn("未注册模块:{}", module);
            return null;
        }
        String redisKey = loaderContext.buildKey(key);
        Object o = redisTemplate.opsForValue().get(redisKey);
        if (o != null) {
            return (R) o;
        }
        IDbCacheLoader<U, R> loader = (IDbCacheLoader<U, R>) loaderContext.loader;
        R dbResult = loader.singleLoad(key);
        if (dbResult != null) {
            redisTemplate.opsForValue().set(redisKey, dbResult, loader.ttlSeconds(), TimeUnit.SECONDS);
            log.info("将模块:{} key :{} 存入redis成功", module, key);
        }
        return dbResult;
    }

    /**
     * 删除缓存
     *
     * @param module
     * @param keys
     * @param <U>
     */
    public <U extends Serializable> void removeCache(String module, Collection<U> keys) {
        LoaderContext loaderContext = loaderMap.get(module);
        if (loaderContext == null) {
            log.warn("未注册缓存模块:{}", module);
            return;
        }
        Set<String> keySet = keys.stream().filter(Objects::nonNull).map(loaderContext::buildKey).collect(Collectors.toSet());
        if (keySet.isEmpty()) {
            log.warn("删除的模块缓存:{}未发现有效key:{}", module, keys);
            return;
        }
        Long delete = redisTemplate.delete(keySet);
        log.info("删除模块:{}缓存:{} 预期:{}个,结果:{}个", module, keys, keySet.size(), delete);
    }


    @Data
    private static class LoaderContext {
        private String keyPrefix;

        private final String module;

        private IDbCacheLoader<?, ?> loader;

        public LoaderContext(String keyPrefix, IDbCacheLoader<?, ?> loader, String module) {
            this.keyPrefix = keyPrefix;
            this.loader = loader;
            this.module = module;
        }

        private <U extends Serializable, R> AbstractMap.SimpleEntry<U, R> convert(R obj) {
            if (obj == null) {
                return null;
            }
            Class<?> objClass = obj.getClass();
            Map<Field, CustomCacheKey> fieldCustomCacheKeyMap = ReflectUtil.getAnnotation(objClass, CustomCacheKey.class);
            if (CollectionUtils.isEmpty(fieldCustomCacheKeyMap)) {
                log.warn("未在实体类:{}发现 @CustomCacheKey 注解", objClass);
                return null;
            }
            Optional<Map.Entry<Field, CustomCacheKey>> keyEntry = fieldCustomCacheKeyMap.entrySet().stream().filter(e -> Objects.equals(module, e.getValue().value())).findAny();
            if (!keyEntry.isPresent()) {
                log.warn("未在实体类:{}发现 {} 模块的缓存key", objClass, module);
                return null;
            }
            Field field = keyEntry.get().getKey();
            return new AbstractMap.SimpleEntry<>((U) ReflectionUtils.getField(field, obj), obj);
        }

        public <U> String buildKey(U key) {
            if (key == null) {
                return null;
            }
            return keyPrefix.concat(key.toString());
        }
    }
}
